<template>
    <div class="cropper">
        <canvas ref="cropperCanvas" style="display: none"></canvas>
        <img :src="imgUrl" alt="" />
        <div
            class="showPosition"
            ref="showPosition"
            :style="`top: ${originHeightOffset + 1}px;
                left: ${originWidthOffset + 1}px;
                width: ${originShowWidth}px;
                height: ${originShowHeight}px;`"
        >
            <!-- 裁剪框 -->
            <div
                class="crop-box"
                ref="cropBox"
                :style="`width: ${boxWidth}px;
                        height: ${boxHeight}px;
                        top: ${boxTop}px;
                        left: ${boxLeft}px;`"
            >
                <!-- 中间移动块 -->
                <div class="crop-box-move" ref="cropBoxMove"></div>
                <!-- 左上控块 -->
                <div
                    class="cropper-point point-lt"
                    @mousedown="(e) => startResize('lt', e)"
                    @touchstart="(e) => startResize('lt', e.touches[0])"
                ></div>
                <!-- 右上控块 -->
                <div
                    class="cropper-point point-rt"
                    @mousedown="(e) => startResize('rt', e)"
                    @touchstart="(e) => startResize('rt', e.touches[0])"
                ></div>
                <!-- 左下控块 -->
                <div
                    class="cropper-point point-lb"
                    @mousedown="(e) => startResize('lb', e)"
                    @touchstart="(e) => startResize('lb', e.touches[0])"
                ></div>
                <!-- 右下控块 -->
                <div
                    class="cropper-point point-rb"
                    @mousedown="(e) => startResize('rb', e)"
                    @touchstart="(e) => startResize('rb', e.touches[0])"
                ></div>
            </div>
            <!-- 上蒙版 -->
            <div
                class="marking marking-top"
                :style="`width: ${boxWidth}px;
                        height: ${boxTop}px;
                        top: 0px;
                        left: ${boxLeft}px;`"
            ></div>
            <!-- 右蒙版 -->
            <div
                class="marking marking-right"
                :style="`width: ${originShowWidth - (boxLeft + boxWidth)}px;
                        height: ${originShowHeight}px;
                        top: 0px;
                        left: ${boxLeft + boxWidth}px;`"
            ></div>
            <!-- 下蒙版 -->
            <div
                class="marking marking-bottom"
                :style="`width: ${boxWidth}px;
                        height: ${originShowHeight - (boxTop + boxHeight)}px;
                        top: ${boxTop + boxHeight}px;
                        left: ${boxLeft}px;`"
            ></div>
            <!-- 左蒙版 -->
            <div
                class="marking marking-left"
                :style="`width: ${boxLeft}px;
                        height: ${originShowHeight}px;
                        top: 0px;
                        left: 0px;`"
            ></div>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, watch, computed } from "vue";
// 整个裁剪组件的宽，挂载时获取
const cropperWidth = ref(0);
// 整个裁剪组件的高，挂载时获取
const cropperHeight = ref(0);
// 原图的宽
const width = ref(1);
// 原图的高
const height = ref(1);
// 裁剪框宽
const boxWidth = ref(0);
// 裁剪框高
const boxHeight = ref(0);
// 裁剪框的上偏移量 上蒙版的高度
const boxTop = ref(0);
// 裁剪框的左偏移量 左蒙版的宽度
const boxLeft = ref(0);
// 是否正在缩放
const isResizing = ref(false);
// 缩放方向
const resizeDirection = ref("");
// 鼠标按下时的X坐标
const startX = ref(0);
// 鼠标按下时的裁剪框宽度
const startBoxWidth = ref(0);
// 鼠标按下时的裁剪框高度
const startBoxHeight = ref(0);
// 鼠标按下时的裁剪框左偏移量
const startBoxLeft = ref(0);
// 鼠标按下时的裁剪框上偏移量
const startBoxTop = ref(0);

// 从 props 中获取预览容器和图片相关的属性
const props = defineProps({
    imgUrl: String, // 图片映射地址
    wrapWidth: Number,  // 预览容器宽
    wrapHeight: Number, // 预览容器高
    prevWidth: Number,  // 预览图实际宽
    prevHeight: Number, // 预览图实际高
    prevOffsetX: Number, // 预览图水平偏移量
    prevOffsetY: Number, // 预览图垂直偏移量
    cutOrder: Boolean,  // 裁剪命令 由 false 变为 true 就裁剪
});

const originAspectRatio = computed(() => width.value / height.value);

const originShowWidth = computed(() => {
    if (originAspectRatio.value < cropperWidth.value / cropperHeight.value) {
        return cropperHeight.value * originAspectRatio.value;
    } else {
        return cropperWidth.value;
    }
});

const originShowHeight = computed(() => {
    if (originAspectRatio.value > cropperWidth.value / cropperHeight.value) {
        return cropperWidth.value / originAspectRatio.value;
    } else {
        return cropperHeight.value;
    }
});

const originWidthOffset = computed(() => {
    if (originAspectRatio.value < cropperWidth.value / cropperHeight.value) {
        const showWidth = cropperHeight.value * originAspectRatio.value;
        return (cropperWidth.value - showWidth) / 2;
    } else {
        return 0;
    }
});

const originHeightOffset = computed(() => {
    if (originAspectRatio.value > cropperWidth.value / cropperHeight.value) {
        const showHeight = cropperWidth.value / originAspectRatio.value;
        return (cropperHeight.value - showHeight) / 2;
    } else {
        return 0;
    }
});

const cutOffsetX = computed(() => Math.floor((boxLeft.value / originShowWidth.value) * width.value));
const cutOffsetY = computed(() => Math.floor((boxTop.value / originShowHeight.value) * height.value));
const cutWidth = computed(() => Math.floor((boxWidth.value / originShowWidth.value) * width.value));
const cutHeight = computed(() => Math.floor((boxHeight.value / originShowHeight.value) * height.value));

const cPrevWidth = computed(() => props.wrapWidth * (originShowWidth.value / boxWidth.value));
const cPrevHeight = computed(() => props.wrapHeight * (originShowHeight.value / boxHeight.value));
const cPrevOffsetX = computed(() => cPrevWidth.value * (boxLeft.value / originShowWidth.value));
const cPrevOffsetY = computed(() => cPrevHeight.value * (boxTop.value / originShowHeight.value));


onMounted(() => {
    const cropperEl = document.querySelector(".cropper");
    cropperWidth.value = cropperEl.clientWidth;  // 减去左右边线的宽度
    cropperHeight.value = cropperEl.clientHeight;    // 减去上下边线的高度
    updateOrigin();    // 初始化原图宽高
    initDrag();    // 初始化拖动，开启监听事件
});

// watch 监视属性变化
watch(() => props.imgUrl, (current) => {
    if (current) {
        updateOrigin();
    }
});

watch(() => props.cutOrder, (current) => {
    if (current) {
        cut(); // 开始裁剪
    }
});

// 对预览参数进行更新
watch(cPrevWidth, (curr) => {
    emit("update:prevWidth", curr);
});

watch(cPrevHeight, (curr) => {
    emit("update:prevHeight", curr);
});

watch(cPrevOffsetX, (curr) => {
    emit("update:prevOffsetX", curr);
});

watch(cPrevOffsetY, (curr) => {
    emit("update:prevOffsetY", curr);
});

// 图片更换时更新原图宽高值，并且更新裁剪框呈现
function updateOrigin() {
    const img = new Image();
    img.src = imgUrl.value;
    img.onload = () => {
        width.value = img.width;
        height.value = img.height;
        setTimeout(() => {
            initBox();     // 初始化裁剪框
        }, 0);    // 异步等待初始值更新完
    }
}

// 初始化裁剪框
function initBox() {
    // 如果是较宽图片，以原图呈现高初始化
    if (originAspectRatio.value > cropperWidth.value / cropperHeight.value) {
        boxHeight.value = 0.9 * originShowHeight.value;
        boxWidth.value = boxHeight.value * (wrapWidth.value / wrapHeight.value);
        boxTop.value = (originShowHeight.value - boxHeight.value) / 2;
        boxLeft.value = (cropperWidth.value - boxWidth.value) / 2;
    }
    // 否则以原图呈现宽初始化
    else {
        boxWidth.value = 0.9 * originShowWidth.value;
        boxHeight.value = boxWidth.value / (wrapWidth.value / wrapHeight.value);
        boxTop.value = (cropperHeight.value - boxHeight.value) / 2;
        boxLeft.value = (originShowWidth.value - boxWidth.value) / 2;
    }
}

const cropBoxMove = ref(null);
const cropBox = ref(null);
const showPosition = ref(null);

// 初始化拖动
function initDrag() {
    let isDragging = false; // 是否正在拖动
    let offsetX, offsetY;
    // 鼠标按下事件处理程序
    cropBoxMove.value.addEventListener("mousedown", (e) => {
        isDragging = true;
        offsetX = e.clientX - cropBox.value.getBoundingClientRect().left; // 鼠标相对crop-box的X偏移
        offsetY = e.clientY - cropBox.value.getBoundingClientRect().top; // 鼠标相对crop-box的Y偏移
    });
    // 在移动端 触摸事件处理程序
    cropBoxMove.value.addEventListener("touchstart", (e) => {
        isDragging = true;
        offsetX = e.touches[0].clientX - cropBox.value.getBoundingClientRect().left;
        offsetY = e.touches[0].clientY - cropBox.value.getBoundingClientRect().top;
    });
    // 鼠标移动事件处理程序
    document.addEventListener("mousemove", (e) => {
        if (!isDragging) return;
        // 计算新的crop-box位置
        let left = e.clientX - offsetX - showPosition.value.getBoundingClientRect().left;
        let top = e.clientY - offsetY - showPosition.value.getBoundingClientRect().top;
        // 限制crop-box在showPosition内部
        left = Math.max(0, left);
        left = Math.min(showPosition.value.offsetWidth - cropBox.value.offsetWidth, left);
        top = Math.max(0, top);
        top = Math.min(showPosition.value.offsetHeight - cropBox.value.offsetHeight, top);
        // 设置新位置
        boxLeft.value = left;
        boxTop.value = top;
    });
    // 在移动端 移动事件处理程序
    document.addEventListener("touchmove", (e) => {
        if (!isDragging) return;
        // 计算新的crop-box位置
        let left = e.touches[0].clientX - offsetX - showPosition.value.getBoundingClientRect().left;
        let top = e.touches[0].clientY - offsetY - showPosition.value.getBoundingClientRect().top;
        // 限制crop-box在showPosition内部
        left = Math.max(0, left);
        left = Math.min(showPosition.value.offsetWidth - cropBox.value.offsetWidth, left);
        top = Math.max(0, top);
        top = Math.min(showPosition.value.offsetHeight - cropBox.value.offsetHeight, top);
        // 设置新位置
        boxLeft.value = left;
        boxTop.value = top;
    });
    // 鼠标释放事件处理程序
    document.addEventListener("mouseup", () => {
        isDragging = false;
    });
    // 在移动端 释放事件处理程序
    document.addEventListener("touchend", () => {
        isDragging = false;
    });
}

// 开始缩放
function startResize(resizeDirection, event) {
    isResizing.value = true;
    resizeDirection.value = resizeDirection;
    // 记录按下时的状态
    startX.value = event.clientX;
    startBoxWidth.value = boxWidth.value;
    startBoxHeight.value = boxHeight.value;
    startBoxLeft.value = boxLeft.value;
    startBoxTop.value = boxTop.value;
    // 添加全局事件监听器
    window.addEventListener('mousemove', resize.value);
    window.addEventListener('touchmove', resize.value);
    window.addEventListener('mouseup', stopResize.value);
    window.addEventListener('touchend', stopResize.value);
}

</script>

<style scoped>
    .cropper {
        height: 100%;
        width: 100%;
        background-color: #000;
        border-radius: 4px;
        border: 1px solid #e5e9ef;
        overflow: hidden;
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
    }

    .cropper > img {
        width: 100%;
        height: 100%;
        object-fit: scale-down;
    }

    .showPosition {
        position: absolute;
    }

    .crop-box {
        position: relative;
        border: 1px solid #fff;
        box-sizing: border-box;
    }

    .crop-box-move {
        z-index: 9;
        position: absolute;
        cursor: move;
        width: 100%;
        height: 100%;
        left: 0;
        top: 0;
    }

    .cropper-point {
        position: absolute;
        background-color: #f6f7f8;
        height: 12.5px;
        opacity: 1;
        width: 12.5px;
        border-radius: 50%;
        z-index: 10;
    }

    .point-lt {
        cursor: nwse-resize;
        left: -6px;
        top: -6px;
    }

    .point-rt {
        cursor: nesw-resize;
        right: -6px;
        top: -6px;
    }

    .point-lb {
        cursor: nesw-resize;
        left: -6px;
        bottom: -6px;
    }

    .point-rb {
        cursor: nwse-resize;
        right: -6px;
        bottom: -6px;
    }

    .marking {
        position: absolute;
        background-color: rgba(0, 0, 0, 0.5);
    }
</style>